﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Common;

namespace Utility
{
    public abstract class DbParameterList<T> : IEnumerable<T>, IEnumerable where T : DbParameter, new()
    {
        private List<T> items;
        private IDictionary<string,int> indexHashCS;
        private IDictionary<string,int> indexHashCI;

        protected DbParameterList()
        {
            items = new List<T>();
            indexHashCS = new Dictionary<string,int>();
            indexHashCI = new Dictionary<string,int>(StringComparer.CurrentCultureIgnoreCase);
        }

        #region Public Methods

        public T this[int index]
        {
            get { return GetParameter(index); }
            set { SetParameter(index, value); }
        }

        public T this[string name]
        {
            get { return GetParameter(name); }
            set { SetParameter(name, value); }
        }

        public T Add(T value)
        {
            return InternalAdd(value, -1);
        }

        public T AddWithValue(string parameterName, object value)
        {
            return Add(new T() { ParameterName = parameterName, Value = value });
        }

        public T[] ToArray()
        {
            return items.ToArray();
        }

        #endregion

        #region DbParameterCollection Implementation

        public void AddRange(Array values)
        {
            foreach (T p in values)
                Add(p);
        }

        private void CheckIndex(int index)
        {
            if (index < 0 || index >= Count)
                throw new IndexOutOfRangeException("Parameter index is out of range.");
        }

        protected T GetParameter(string parameterName)
        {
            int index = IndexOf(parameterName);
            if (index < 0)
            {
                throw new ArgumentException("Parameter '" + parameterName + "' not found in the collection.");
            }
            return items[index];
        }

        protected T GetParameter(int index)
        {
            CheckIndex(index);
            return items[index];
        }

        protected void SetParameter(string parameterName, T value)
        {
            int index = IndexOf(parameterName);
            if (index < 0)
                throw new ArgumentException("Parameter '" + parameterName + "' not found in the collection.");
            SetParameter(index, value);
        }

        protected void SetParameter(int index, T value)
        {
            CheckIndex(index);
            T p = items[index];

            // first we remove the old parameter from our hashes
            indexHashCS.Remove(p.ParameterName);
            indexHashCI.Remove(p.ParameterName);

            // then we add in the new parameter
            items[index] = value;
            indexHashCS.Add(value.ParameterName, index);
            indexHashCI.Add(value.ParameterName, index);
        }

        public int Add(object value)
        {
            T parameter = value as T;
            if (parameter == null)
                throw new ArgumentNullException("value", "Only DbParameter objects may be stored");

            if (string.IsNullOrEmpty(parameter.ParameterName))
                throw new ArgumentNullException("value", "Parameters must be named");

            parameter = Add(parameter);
            return IndexOf(parameter);
        }

        public void Clear()
        {
            items.Clear();
            indexHashCS.Clear();
            indexHashCI.Clear();
        }

        public bool Contains(string parameterName)
        {
            return IndexOf(parameterName) != -1;
        }

        public bool Contains(object value)
        {
            T parameter = value as T;
            if (parameter == null)
                throw new ArgumentNullException("value", "Only DbParameter objects may be stored");
            return items.Contains(parameter);
        }

        public void CopyTo(Array array, int index)
        {
            items.ToArray().CopyTo(array, index);
        }

        public int Count
        {
            get { return items.Count; }
        }

        public IEnumerator<T> GetEnumerator()
        {
            return items.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public int IndexOf(string parameterName)
        {
            if (indexHashCI.ContainsKey(parameterName))
            {
                return indexHashCI[parameterName];
            }

            if (indexHashCS.ContainsKey(parameterName))
            {
                return indexHashCS[parameterName];
            }

            return -1;
        }

        public int IndexOf(object value)
        {
            T parameter = value as T;
            if (parameter == null)
                throw new ArgumentNullException("value", "Only DbParameter objects may be stored");
            return items.IndexOf(parameter);
        }

        public void Insert(int index, object value)
        {
            T parameter = value as T;
            if (parameter == null)
                throw new ArgumentNullException("value", "Only DbParameter objects may be stored");
            InternalAdd(parameter, index);
        }

        public bool IsFixedSize
        {
            get { return (items as IList).IsFixedSize; }
        }

        public bool IsReadOnly
        {
            get { return (items as IList).IsReadOnly; }
        }

        public bool IsSynchronized
        {
            get { return (items as IList).IsSynchronized; }
        }

        public void Remove(object value)
        {
            T parameter = value as T;
            if (parameter == null)
                throw new ArgumentNullException("value", "Only DbParameter objects may be stored");

            int index = IndexOf(parameter);
            items.Remove(parameter);

            indexHashCS.Remove(parameter.ParameterName);
            indexHashCI.Remove(parameter.ParameterName);
            AdjustHashes(index, false);
        }

        public void RemoveAt(string parameterName)
        {
            T parameter = GetParameter(parameterName);
            Remove(parameter);
        }

        public void RemoveAt(int index)
        {
            object o = items[index];
            Remove(o);
        }

        public object SyncRoot
        {
            get { return (items as IList).SyncRoot; }
        }

        #endregion

        private T InternalAdd(T value, int index)
        {
            if (value == null)
                throw new ArgumentNullException("value", "Only DbParameter objects may be stored");

            // make sure we don't already have a parameter with this name
            if (IndexOf(value.ParameterName) >= 0)
            {
                throw new ArgumentException("DdParameter exists", value.ParameterName);
            }

            if (index == -1)
            {
                items.Add(value);
                index = items.IndexOf(value);
            }
            else
            {
                items.Insert(index, value);
                AdjustHashes(index, true);
            }

            indexHashCS.Add(value.ParameterName, index);
            indexHashCI.Add(value.ParameterName, index);

            return value;
        }

        private void AdjustHashes(int keyIndex, bool addEntry)
        {
            for (int i = 0; i < Count; i++)
            {
                string name = items[i].ParameterName;
                AdjustHash(indexHashCI, name, keyIndex, addEntry);
                AdjustHash(indexHashCS, name, keyIndex, addEntry);
            }
        }

        private static void AdjustHash(IDictionary<string,int> hash, string parameterName, int keyIndex, bool addEntry)
        {
            if (!hash.ContainsKey(parameterName)) return;
            int index = hash[parameterName];
            if (index < keyIndex) return;
            hash[parameterName] = addEntry ? ++index : --index;
        }
    }
}